/**
 * Friendly Interactive Recursion Educator (FIRE)
 * COMS 4115: Programming Languages & Translators
 * Prof. Aho
 * 
 * @author Brian Lu
 * @author Sahar Hasan
 * @author Steve Hehl
 * @author Tristan Naumann
 * @version 2009-05-12
 */
package fire;

import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import javax.swing.AbstractButton;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextField;

public class Fire extends JPanel{
    static protected JButton fow, back, start, change, zoomIn, zoomOut;
    static protected JScrollPane scrollpane;
    private static int currNode = 0;
    private static int nodeCount = 1;
    private static int totalSteps = 0;
    static protected Tree t;
    private static int maxDepth = 0;
    private static boolean makeDraw = false;
    private static ArrayList<JTextField> textFields = new ArrayList <JTextField>();
    private static ArrayList<JLabel> labels = new ArrayList <JLabel>();
    private static ArrayList<String> values = new ArrayList<String>();
    private static Object[] valuesAsObjects;
    private static String errMessage;
    private static Fire fire;
    private static double xscale = 1, yscale = 1;
    private static int yboxsize = 140, xboxsize = 200;

    public Fire() {
    	
    	setLayout(null);
		setBackground(Color.white);
		setForeground(Color.black);
    }
    
    
    public static int getMaxDepth()
    {
    	return maxDepth;
    }
    
    public static void setMaxDepth(int m)
    {
    	maxDepth = m;
    }
    
    public static void setTotalSteps(int s)
    {
    	totalSteps = s;
    }
    
    public static int getTotalSteps()
    {
    	return totalSteps;
    }
    
    protected void paintComponent(Graphics g) {
    	
		g.setColor(getBackground());
		g.fillRect(0, 0, getWidth(), getHeight());
		g.setColor(getForeground());
		g.setFont(new Font("Comic Sans MS",Font.PLAIN, (int)(8 * xscale)));
		currNode = 0;
		if(makeDraw)
			this.drawTree(g, t.root);
		
		revalidate();
	}
    
    public void drawTree(Graphics g, Node root) {
    	int dx, dy, dx2, dy2, textYPos, parxpos, parypos, xpos, ypos;
    	xpos = (int) (root.getXpos() * xscale);
    	ypos = (int) (root.getYpos() * yscale) + 15;
    	
		if (root != null)
		{
			Node p = root.getParent();
			
			if(p != null)
			{
				parxpos = (int)(p.getXpos() * xscale);
				parypos = (int) (p.getYpos() * yscale) + 15;
				dx = (int)(parxpos + (xboxsize / 2.0));
				dy = (int)(parypos + (yboxsize));
				dx2 = (int) (xpos + (xboxsize / 2.0)); 
				dy2 = ypos;
				if(currNode == nodeCount - 1)
				{
					g.setColor(Color.RED);
				}
				else
				{
					g.setColor(new Color(117, 178, 221));
				}
				g.drawLine(dx,dy,dx2,dy2);
			}
			
			textYPos = (int) (ypos + (10 * yscale));
			if(currNode == nodeCount - 1)
			{
				g.setColor(Color.RED);
				g.fillRect((int)(xpos - (2 * xscale)), (int)(ypos - (2 * yscale)), (int)(xboxsize * (1.02)), (int)(yboxsize * 1.03));
			}
			
			g.setColor(new Color(117, 178, 221));
			g.fillRect(xpos, ypos, (int)(xboxsize), (int)(yboxsize));
			if(currNode == nodeCount - 1)
			{
				g.setColor(Color.RED);
			}
			else
			{
				g.setColor(new Color(0, 0, 128));
			}
			
			textYPos = (int) stringDrawer(g, root.getValues(), (int) (xpos + (10 * xscale)), textYPos);
			textYPos = (int) stringDrawer(g, root.getCall(), (int) (xpos + (10 * xscale)), textYPos);
			
			for(int i = 0; i < root.getChildren().size(); i++)
			{
				if(root.getChildren().get(i)!= null && currNode < nodeCount - 1)
				{
					root.getChildren().get(i).setChildNum(i);
					currNode += 1;
					drawTree(g, root.getChildren().get(i));
				}
			}

			if(currNode < nodeCount - 1)
			{
				currNode += 1;
				g.setFont(new Font("Comic Sans MS",Font.PLAIN, (int)(10 * xscale)));
				
				textYPos += (40 * yscale);
				if(currNode == nodeCount - 1)
				{
					g.setColor(Color.RED);
					textYPos = (int) stringDrawer(g, "RETURN " + root.getRetVal(), (int) (xpos + (10 * xscale)), textYPos);
				}
				else
				{
					g.setColor(new Color(0, 0, 128));
					textYPos = (int) stringDrawer(g, "RETURN " + root.getRetVal(), (int) (xpos + (10 * xscale)), textYPos);
					g.setColor(new Color(117, 178, 221));
				}
				
				if(p != null)
				{
					parxpos = (int)(p.getXpos() * xscale);
					parypos = (int) (p.getYpos() * yscale) + 15;
					dx = (int) (parxpos + (xboxsize / 2.9));
					dy = (int) (parypos + (yboxsize));
					dx2 = (int) (xpos + (xboxsize / 2.9)); 
					dy2 = ypos;
					
					g.drawLine(dx,dy,dx2,dy2);
				}
				
				g.setFont(new Font("Comic Sans MS",Font.PLAIN, (int)(8 * xscale)));
			}
		}
    }
    
    public static double stringDrawer(Graphics g, String s, int xpos, int ypos)
    {
    	if(s.length() < 40)
    	{
    		g.drawString(s, xpos, ypos);
    		return ypos + (10 * xscale);
    	}
    	else
    	{
    		g.drawString(s.substring(0, 40), xpos, ypos);
    		return stringDrawer(g, s.substring(40), xpos, (int) (ypos + (10 * yscale)));
    	}
    }
    
    public static void convertToWrappers(ArrayList<String> params)
    {
    	valuesAsObjects = new Object[params.size() + 1];
    	try{
    		for(int i = 0; i < params.size(); i++)
    		{
    			if(params.get(i).equals("int"))
    			{
    				valuesAsObjects[i] = Integer.parseInt(values.get(i));
    			}
    			else if(params.get(i).equals("boolean"))
    			{
    				valuesAsObjects[i] = Boolean.parseBoolean(values.get(i));
    			}
    			else if(params.get(i).equals("double"))
    			{
    				valuesAsObjects[i] = Double.parseDouble(values.get(i));
    			}
    		}
    	}
    	catch(NumberFormatException e)
    	{
    		errMessage = "I'm Sorry, your inputs were not of the correct formats. Please to run your program again.";
    		System.out.println(errMessage);
    		System.exit(0);
    	}
    	
    	valuesAsObjects[params.size()] = null;
    }
    
    public static void main(String[] args) {
        JFrame frame = new JFrame("Fire GUI");

        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        fire = new Fire();
		scrollpane = new JScrollPane(fire, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		scrollpane.setPreferredSize(new Dimension(1000, 600));
        
    	FireHelper.makeLists();
		
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(null);
		buttonPanel.setForeground(Color.black);
		buttonPanel.setPreferredSize(new Dimension(1000, 50));
		JPanel inputPanel = new JPanel();
		buttonPanel.add(inputPanel);
		inputPanel.setLayout(new FlowLayout());
		
		for(int i = 0; i < FireHelper.paramList.size(); i++)
		{
			textFields.add(new JTextField(10));
			labels.add(new JLabel("param" + (i + 1) + " (" + FireHelper.paramList.get(i) + "):"));
			inputPanel.add(labels.get(i));
			inputPanel.add(textFields.get(i));
		}
        
        fow = new JButton("Step Forward >>");
        fow.setVerticalTextPosition(AbstractButton.CENTER);
        fow.setHorizontalTextPosition(AbstractButton.LEFT);
        fow.setMnemonic(KeyEvent.VK_D);
        fow.setActionCommand("step");
        fow.addActionListener(new
        		ActionListener()
        		{
        			public void actionPerformed(ActionEvent e) {
        		        if (e.getActionCommand().equals("step")) {
        		            nodeCount += 1;
        		            if(nodeCount == totalSteps)
        		            {
        		            	fow.setEnabled(false);
        		            }
        		            if(!back.isEnabled())
        		            {
        		            	back.setEnabled(true);
        		            }
        		            fire.repaint();
        		        }
        		    }
        		});
        fow.setToolTipText("Click this button to step forward through recursion.");
        fow.setLocation(230, 10);
        fow.setSize(200, 30);
        fow.setEnabled(false);
        buttonPanel.add(fow);
        
        back = new JButton("<< Step Backward");
        back.setVerticalTextPosition(AbstractButton.CENTER);
        back.setHorizontalTextPosition(AbstractButton.LEFT);
        back.setMnemonic(KeyEvent.VK_D);
        back.setActionCommand("back");
        back.addActionListener(new
        		ActionListener()
        		{
        			public void actionPerformed(ActionEvent e)
        			{
        				if (e.getActionCommand().equals("back")) {
        		        	nodeCount -= 1;
        		        	if(nodeCount == 1)
        		        	{
        		        		back.setEnabled(false);
        		        	}
        		        	if(!fow.isEnabled())
        		        	{
        		        		fow.setEnabled(true);
        		        	}
        			        	
        		        	fire.repaint();
        		        }
        			}
        		});
        back.setToolTipText("Click this button to step backwards through recursion.");
        back.setLocation(10, 10);
        back.setSize(200, 30);
        back.setEnabled(false);
        buttonPanel.add(back);
        
        zoomIn = new JButton("Zoom In");
        zoomIn.setVerticalTextPosition(AbstractButton.CENTER);
        zoomIn.setHorizontalTextPosition(AbstractButton.LEFT);
        zoomIn.setMnemonic(KeyEvent.VK_I);
        zoomIn.setActionCommand("in");
        zoomIn.addActionListener(new
        		ActionListener()
        		{
        			public void actionPerformed(ActionEvent e) {
        		        if (e.getActionCommand().equals("in")) {
        		        	xscale *= 2;
        		        	yscale *= 2;
        		        	xboxsize = (int)(xboxsize * 2.0);
        		        	yboxsize = (int) (yboxsize * 2.0);
        		        	zoomOut.setEnabled(true);
        		        	
        		        	if(xscale >= 4)
        		        	{
        		        		zoomIn.setEnabled(false);
        		        	}
        		        	
        		            fire.repaint();
        		        }
        		    }
        		});
        zoomIn.setToolTipText("Zoom In");
        zoomIn.setLocation(770, 10);
        zoomIn.setSize(100, 30);
        zoomIn.setEnabled(false);
        buttonPanel.add(zoomIn);
        
        zoomOut = new JButton("Zoom Out");
        zoomOut.setVerticalTextPosition(AbstractButton.CENTER);
        zoomOut.setHorizontalTextPosition(AbstractButton.LEFT);
        zoomOut.setMnemonic(KeyEvent.VK_O);
        zoomOut.setActionCommand("out");
        zoomOut.addActionListener(new
        		ActionListener()
        		{
        			public void actionPerformed(ActionEvent e) {
        		        if (e.getActionCommand().equals("out")) {
        		        	xscale /= 2;
        		        	yscale /= 2;
        		        	xboxsize = (int)(xboxsize / 2.0);
        		        	yboxsize = (int) (yboxsize / 2.0);
        		        	zoomIn.setEnabled(true);
        		        	
        		        	if(xscale <= .2)
        		        	{
        		        		zoomOut.setEnabled(false);
        		        	}
        		        	
        		            fire.repaint();
        		        }
        		    }
        		});
        zoomOut.setToolTipText("Zoom Out");
        zoomOut.setLocation(890, 10);
        zoomOut.setSize(100, 30);
        zoomOut.setEnabled(false);
        buttonPanel.add(zoomOut);
		
        start = new JButton("Start");
        start.setVerticalTextPosition(AbstractButton.CENTER);
        start.setHorizontalTextPosition(AbstractButton.LEFT);
        start.setMnemonic(KeyEvent.VK_D);
        start.setActionCommand("start");
        start.addActionListener(new
        		ActionListener()
        		{
        			public void actionPerformed(ActionEvent e) {
        		        if (e.getActionCommand().equals("start")) {
        		        	for(int i = 0; i < textFields.size(); i++)
        		        	{
        		        		values.add(textFields.get(i).getText());
        		        	}
        		        	
        		        	convertToWrappers(FireHelper.paramList);
        		        	
        		            try {
								FireHelper.doRecursive(valuesAsObjects);
							} catch (IllegalArgumentException e1) {
								errMessage = "Hmm... I'm sorry, something went wrong with your input values, please try again.";
								System.out.println(errMessage);
					    		System.exit(0);
							} catch (ClassNotFoundException e1) {
								errMessage = "Hmm... I'm sorry, something went wrong with your input values, please try again.";
								System.out.println(errMessage);
					    		System.exit(0);
							} catch (InstantiationException e1) {
								errMessage = "Hmm... I'm sorry, something went wrong with your input values, please try again.";
								System.out.println(errMessage);
					    		System.exit(0);
							} catch (IllegalAccessException e1) {
								errMessage = "Hmm... I'm sorry, something went wrong with your input values, please try again.";
								System.out.println(errMessage);
					    		System.exit(0);
							} catch (InvocationTargetException e1) {
								errMessage = "Hmm... I'm sorry, something went wrong with your input values, please try again.";
								System.out.println(errMessage);
								System.exit(0);
							}
        		            start.setEnabled(false);
        		            fow.setEnabled(true);
        		            change.setEnabled(true);
        		            zoomIn.setEnabled(true);
        		            zoomOut.setEnabled(true);
        		            t = new Tree(FireHelper.root);
        		            t.countLeaves(t.root);
        		        	t.treeWidth(t.root);
        		        	t.computeNodePositions(t.root, 10);
        		        	fire.setPreferredSize(new Dimension(FireHelper.root.getWidth() * 4, (maxDepth + 1) * 800));
        		        	makeDraw = true;
        		        	fire.repaint();
        		        }
        		    }
        		});
        start.setToolTipText("Start Recursion.");
        start.setLocation(450, 10);
        start.setSize(100, 30);
        inputPanel.add(start);
        
        change = new JButton("Change Values");
        change.setVerticalTextPosition(AbstractButton.CENTER);
        change.setHorizontalTextPosition(AbstractButton.LEFT);
        change.setMnemonic(KeyEvent.VK_D);
        change.setActionCommand("change");
        change.addActionListener(new
        		ActionListener()
        		{
        			public void actionPerformed(ActionEvent e) {
        		        if (e.getActionCommand().equals("change")) {
        		        	FireHelper.root = null;
        		        	currNode = 0;
        		            nodeCount = 1;
        		            totalSteps = 0;
        		            maxDepth = 0;
        		            values = new ArrayList <String>();
        		            System.out.println(values.size());
        		        	makeDraw = false;
        		        	fow.setEnabled(false);
        		        	back.setEnabled(false);
        		        	start.setEnabled(true);
        		        	change.setEnabled(false);
        		        	zoomIn.setEnabled(false);
        		        	zoomOut.setEnabled(false);
        		        	fire.repaint();
        		        }
        		    }
        		});
        change.setToolTipText("Change Values of Recursion.");
        change.setSize(100, 30);
        change.setEnabled(false);
        inputPanel.add(change);
        
        
        
		frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
		frame.getContentPane().add(inputPanel);
		frame.getContentPane().add(buttonPanel);
		frame.getContentPane().add(scrollpane);
        frame.pack();
        frame.setVisible(true);
    }
}
